from enum import Enum
import glm
from common import PerspectiveCamera

class MouseButton(Enum):
    NONE = 0
    Left = 1
    Right = 2

class TrackballCamera(PerspectiveCamera):
    def __init__(self, fov: float, aspect: float, near: float, far: float, 
                 position: glm.vec3, center:glm.vec3, up:glm.vec3) -> None:
        super().__init__(fov, aspect, near, far, position, center, up)
        self.initTransformation()
        self.mouse_position = glm.vec2(-2.0)
        self.mouse_state = MouseButton.NONE
        self.zoom_speed = 0.3
        self.pan_speed = 0.5

    def initTransformation(self)->None:
        direction = self.center - self.position
        z_axis = glm.normalize(direction)
        x_axis = glm.normalize(glm.cross(z_axis, glm.normalize(self.up)))
        y_axis = glm.normalize(glm.cross(x_axis, z_axis))
        x_axis = glm.normalize(glm.cross(z_axis, y_axis))
        self.center_translation = glm.inverse(glm.translate(self.center))
        self.translation = glm.translate(glm.vec3(0.0, 0.0, -glm.length(direction)))
        self.rotation = glm.normalize(glm.quat_cast(glm.transpose(
            glm.mat3(x_axis, y_axis, -z_axis))))
        self.view_matrix = self.translation * glm.mat4_cast(self.rotation) * \
            self.center_translation
        self.inverse_view_matrix = glm.inverse(self.view_matrix)

    def reset(self, position: glm.vec3, center:glm.vec3, up:glm.vec3) -> None:
        self.position = position
        self.center = center
        self.up = up
        self.initTransformation()

    def setWindowSize(self, width:int, height:int)->None:
        self.window_width = width
        self.window_height = height
    
    def mouseOnSphere(self, pos:glm.vec2)->glm.vec2:
        return glm.vec2(pos.x * 2.0 / self.window_width - 1.0, 1.0 - 2.0 * 
                        pos.y / self.window_height)
    
    def setMosueState(self, state:MouseButton)->None:
        self.mouse_state = state

    def mouseMove(self, x:float, y:float)->None:
        new_position = self.mouseOnSphere(glm.vec2(x, y))
        if (self.mouse_position - glm.vec2(-2.0)) != glm.vec2(0):
            if self.mouse_state == MouseButton.Left:
                self.rotate(self.mouse_position, new_position)
            elif self.mouse_state == MouseButton.Right:
                dxy = new_position - self.mouse_position
                inverse_projection_matrix = glm.inverse(self.projection_matrix)
                dxy4 = inverse_projection_matrix * glm.vec4(dxy.x, dxy.y, 0, 1)
                self.pan(glm.vec2(dxy4.x, dxy4.y))
        self.mouse_position = new_position

    def updateCamera(self)->None:
        self.view_matrix = self.translation * glm.mat4_cast(self.rotation) * \
            self.center_translation
        self.inverse_view_matrix = glm.inverse(self.view_matrix)
        self.position = glm.vec3(self.inverse_view_matrix * glm.vec4(0,0,0,1))
        self.center =  -glm.vec3(self.center_translation * glm.vec4(0,0,0,1))
        self.up = glm.normalize(glm.vec3(self.inverse_view_matrix * 
                                         glm.vec4(0, 1, 0, 0)))

    def screenToTrackball(self, p:glm.vec2)->glm.quat:
        distance = glm.dot(p, p)
        if distance <= 1.0:
            return glm.quat(0.0, p.x, p.y, glm.sqrt(1.0 - distance))
        else:
            projection = glm.normalize(p)
            return glm.quat(0.0, projection.x, projection.y, 0.0)

    def rotate(self, old_mouse:glm.vec2, new_mouse:glm.vec2)->None:
        new_position = glm.clamp(new_mouse, glm.vec2(-1, -1), glm.vec2(1, 1))
        old_position = glm.clamp(old_mouse, glm.vec2(-1, -1), glm.vec2(1, 1))

        new_trackball = self.screenToTrackball(new_position)
        old_trackball = self.screenToTrackball(old_position)
        self.rotation = new_trackball * old_trackball * self.rotation
        self.updateCamera()
        
    def pan(self, mouse_delta:glm.vec2)->None:
        pan_amount = abs(self.translation[3][2])
        motion = glm.vec4(mouse_delta.x * pan_amount, mouse_delta.y * 
                          pan_amount, 0.0, 0.0)
        motion = self.inverse_view_matrix * motion * self.pan_speed
        self.center_translation = glm.translate(glm.vec3(motion)) * \
            self.center_translation
        self.updateCamera()

    def zoom(self, offset:float)->None:
        motion = glm.vec3(0.0, 0.0, offset) * self.zoom_speed
        self.translation = glm.translate(motion) * self.translation
        self.updateCamera()


        
